<template>
  <div class="app-container">
    <el-row :gutter="10" class="mb8">
      <AssetPathWizardOnly @refresh="refreshByParent" />
      <!-- <ExcelUploader
          action="/sbtz/sbtz/excel"
          template-url="/sbtz/sbtz/template"
          template-name="设备导入模板.xlsx"
          :data="{ beType: currentBeType }"         
          @success="onImported"
        /> -->

      <!-- <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          :disabled="selectedIds.length === 0"
          @click="confirmBatchDelete"
        >
          批量删除（{{ selectedIds.length }}）
        </el-button>
      </el-col> -->
      <el-button type="primary" @click="openDialog">新增母设备</el-button>
      <MachineAddDialog ref="machineAddRef" @refresh="loadList" />
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="Download"
          @click="handleExport"
          v-hasPermi="['sbtz:sbtz:export']"
          >导出</el-button
        >
      </el-col>
    </el-row>

    <!-- 母设备表格 -->
    <el-table
      :data="motherRows"
      border
      stripe
      @row-dblclick="openChildrenDialog"
      :row-class-name="rowClassName"
      @sort-change="mainSortChange"
      v-loading="dblBusy"
      height="680"
      element-loading-text="正在加载子层数据…"
      element-loading-background="rgba(255,255,255,.6)"
    >
      <el-table-column fixed="left" label="母设名称" min-width="130">
        <!-- ★ 新增：表头筛选（母设备 名称/编码） -->
        <template #header>
          <ThRemoteSelect
            v-model="filters.muName.display"
            label="名称"
            :fetch-options="fetchMuName"
            :min-chars="2"
            placeholder="下拉搜索"
            @change="
              (val) => {
                const { text, id } = normalizeSelectChange(val);
                filters.muName.text = text;
                filters.muName.id = id;
                scheduleSearch();
              }
            "
            @clear="
              () => {
                filters.muName = { display: null, text: '', id: null };
                scheduleSearch();
              }
            "
            @visible-change="
              (v) => {
                if (v) {
                  /* 首开已内置预取，无需额外处理 */
                }
              }
            "
          />
        </template>

        <template #default="{ row }">
          <div class="cell-flex">
            <div class="cell-left">
              <!-- <el-icon><Setting /></el-icon> -->
              <el-tag size="small" type="primary">母</el-tag>
              <span class="name">{{ row.name }}</span>
            </div>
            <!-- <div class="cell-right">
              <el-tag size="small" type="info">{{ row.code }}</el-tag>
              <el-tag size="small" type="primary">母设备</el-tag>
            </div> -->
          </div>
        </template>
      </el-table-column>

      <el-table-column label="母设编码" width="200" align="left">
        <template #header>
          <ThRemoteSelect
            v-model="filters.MuCode.display"
            label="母设编码"
            :fetch-options="fetchMuCode"
            :min-chars="1"
            @change="
              (val) => {
                const { text, id } = normalizeSelectChange(val);
                filters.MuCode.text = text;
                filters.MuCode.id = id;
                scheduleSearch();
              }
            "
            @clear="
              () => {
                filters.MuCode = { display: null, text: '', id: null };
                scheduleSearch();
              }
            "
          />
        </template>
        <template #default="{ row }">
          {{ row.code }}
        </template>
      </el-table-column>
      <el-table-column label="系统名称" min-width="200">
        <template #header>
          <ThRemoteSelect
            v-model="filters.systemName.display"
            label="系统名称"
            :fetch-options="fetchSystemName"
            :min-chars="2"
            @change="
              (val) => {
                const { text, id } = normalizeSelectChange(val);
                filters.systemName.text = text;
                filters.systemName.id = id;
                scheduleSearch();
              }
            "
            @clear="
              () => {
                filters.systemName = { display: null, text: '', id: null };
                scheduleSearch();
              }
            "
            @visible-change="
              (v) => {
                if (v) {
                  /* 首开已内置预取，无需额外处理 */
                }
              }
            "
          />
        </template>
        <template #default="{ row }">{{ parentName(row) }}</template>
      </el-table-column>

      <el-table-column label="系统编码" width="180" align="left">
        <template #header>
          <ThRemoteSelect
            v-model="filters.systemCode.display"
            label="系统编码"
            :fetch-options="fetchSystemCode"
            :min-chars="1"
            :prefetch-on-open="true"
            @change="
              (val) => {
                const { text, id } = normalizeSelectChange(val);
                filters.systemCode.text = text;
                filters.systemCode.id = id;
                scheduleSearch();
              }
            "
            @clear="
              () => {
                filters.systemCode = { display: null, text: '', id: null };
                scheduleSearch();
              }
            "
          />
        </template>
        <template #default="{ row }">
          {{ parentCode(row) || "—" }}
        </template>
      </el-table-column>

      <!-- <el-table-column label="层级路径" min-width="360" show-overflow-tooltip>
        <template #default="{ row }">{{ fullPathOf(row) }}</template>
      </el-table-column> -->

      <el-table-column label="设备规格" width="160">
        <template #default="{ row }">
          {{ row.specification || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备型号" width="160">
        <template #default="{ row }">
          {{ row.model || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备材质" width="160">
        <template #default="{ row }">
          {{ row.material || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备品牌" width="160">
        <template #default="{ row }">
          {{ row.brand || "—" }}
        </template>
      </el-table-column>

      <el-table-column label="设备类别" width="160">
        <template #default="{ row }">
          {{ row.beType || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备区域" width="160">
        <template #default="{ row }">
          {{ row.area || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备模块" width="160">
        <template #default="{ row }">
          {{ row.mokuai || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设计温度(℃)" width="160">
        <template #default="{ row }">
          {{ row.designTemp || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设计压力(MPa)" width="160">
        <template #default="{ row }">
          {{ row.designPress || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="电机功率(KW)" width="160">
        <template #default="{ row }">
          {{ row.motorPower || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="搅拌形式" width="160">
        <template #default="{ row }">
          {{ row.mixingType || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备单位" width="160">
        <template #default="{ row }">
          {{ row.unit || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备数量" width="160">
        <template #default="{ row }">
          {{ row.quantity || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备金额(万)" width="160">
        <template #default="{ row }">
          {{ row.amount || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备品牌" width="160">
        <template #default="{ row }">
          {{ row.brand || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="制造厂家" width="160">
        <template #default="{ row }">
          {{ row.manufacturer || "—" }}
        </template>
      </el-table-column>

      <!-- <el-table-column label="备件信息" header-align="center" align="center">
        
        <el-table-column label="备件名称" min-width="160" align="left">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line one-line"
              >
                <el-tooltip
                  :content="sp.code ? `${sp.name}（${sp.code}）` : sp.name"
                  placement="top"
                >
                  <span>{{ sp.name || "-" }}</span>
                </el-tooltip>
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>

         
        <el-table-column label="总库存" width="90" align="center">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack num-col"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line"
              >
                {{ sp.stockTotal ?? "-" }}
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>

         
        <el-table-column label="当前库存" width="100" align="center">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack num-col"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line"
              >
                {{ sp.stockAvailable ?? "-" }}
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>

        <el-table-column label="已用数量" width="100" align="center">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack num-col"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line"
              >
                {{ sp.stockTotal - sp.stockAvailable ?? "-" }}
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>
      </el-table-column> -->

      <el-table-column
        prop="beTypeFirst"
        label="初次类别"
        width="120"
        sortable="custom"
      >
      </el-table-column>

      <el-table-column
        prop="createTime"
        label="创建时间"
        width="120"
        sortable="custom"
      >
        <template #default="{ row }">
          <el-tooltip
            v-if="row.createTime"
            effect="dark"
            :content="parseTime(row.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
            placement="top"
          >
            <span>{{ parseTime(row.createTime, "{y}-{m}-{d}") }}</span>
          </el-tooltip>
          <span v-else>-</span>
        </template>
      </el-table-column>

      <el-table-column
        label="更新时间"
        prop="updateTime"
        width="120"
        sortable="custom"
      >
        <template #default="{ row }">
          <el-tooltip
            v-if="row.updateTime"
            effect="dark"
            :content="parseTime(row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}')"
            placement="top"
          >
            <span>{{ parseTime(row.updateTime, "{y}-{m}-{d}") }}</span>
          </el-tooltip>
          <span v-else>-</span>
        </template>
      </el-table-column>

      <el-table-column label="操作" width="110" fixed="right" align="center">
        <template #default="{ row }">
          <div class="op-grid">
            <div class="op-cell">
              <el-tooltip content="详情">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Search"
                  @click.stop="openDetail(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="修改">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Edit"
                  @click.stop="openEdit(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="新增子项">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Plus"
                  @click.stop="openAddChild(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="历史" placement="top">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Timer"
                  @click.stop="getHistory(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="复制路径">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="CopyDocument"
                  @click.stop="copyPath(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="删除">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  type="danger"
                  :icon="Close"
                  @click.stop="removeNode(row)"
                />
              </el-tooltip>
            </div>
          </div>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total > 0"
      :total="total"
      v-model:page="q.pageNum"
      v-model:limit="q.pageSize"
      @pagination="runSearch"
    />

    <!-- <div v-show="total > 0" class="pagination-affix-wrap"> 
  <el-affix :target="affixTarget" position="bottom" :offset="0">
    <div class="pagination-affix">
      <pagination
        :total="total"
        v-model:page="q.pageNum"
        v-model:limit="q.pageSize"
        @pagination="runSearch"
      />
    </div>
  </el-affix>
</div> -->

    <!-- 子设备弹窗（卡片式，每行 2 个） -->

    <!-- 详情弹窗 -->
    <el-dialog v-model="detailVisible" title="节点详情" width="80%">
      <el-descriptions :column="2" border>
        <el-descriptions-item label="设备路径" :span="2">
          <el-tooltip :content="fullPathOf(detailRow)" placement="top">
            <span class="one-line">{{ fullPathOf(detailRow) }}</span>
          </el-tooltip>
        </el-descriptions-item>
        <el-descriptions-item label="设备名称">{{
          detailRow?.name || "-"
        }}</el-descriptions-item>
        <el-descriptions-item label="设备编码">{{
          detailRow?.code || "-"
        }}</el-descriptions-item>
        <el-descriptions-item label="设备类型">{{
          detailRow?.type || "-"
        }}</el-descriptions-item>
        <!-- <el-descriptions-item label="设备层级">{{ detailRow?.level ?? '-' }}</el-descriptions-item> -->

        <el-descriptions-item label="创建时间">
          {{
            detailRow?.createTime
              ? parseTime(detailRow.createTime, "{y}-{m}-{d} {h}:{i}:{s}")
              : "-"
          }}
        </el-descriptions-item>
        <el-descriptions-item label="更新时间">
          {{
            detailRow?.updateTime
              ? parseTime(detailRow.updateTime, "{y}-{m}-{d} {h}:{i}:{s}")
              : "-"
          }}
        </el-descriptions-item>
        <el-descriptions-item label="备注说明" :span="2">
          <span>{{ detailRow?.remark || "-" }}</span>
        </el-descriptions-item>

        <el-descriptions-item label="备件信息" :span="2">
          <div
            class="spares-grid"
            :style="{ '--cols': totalCols }"
            v-if="
              Array.isArray(detailRow?.spareInfos) &&
              detailRow.spareInfos.length
            "
          >
            <!-- 表头 -->
            <div class="sp-head name">名称</div>
            <div class="sp-head code">编码</div>
            <div class="sp-head">当前库存</div>
            <div class="sp-head">最低库存</div>
            <div class="sp-head">最高库存</div>

            <!-- 行 -->
            <template v-for="(s, i) in detailRow.spareInfos" :key="s.id ?? i">
              <div class="sp-cell sp-name" :title="s?.name">
                <span class="truncate">{{ s?.name ?? "—" }}</span>
              </div>
              <div class="sp-cell sp-code">
                <el-tag v-if="s?.code" size="small" class="code-pill">{{
                  s.code
                }}</el-tag>
                <span v-else>—</span>
              </div>
              <div class="sp-cell">
                <span class="badge info">{{ s?.stockAvailable ?? 0 }}</span>
              </div>
              <div class="sp-cell">
                <span class="badge">{{ s?.stockTotal ?? 0 }}</span>
              </div>
              <div class="sp-cell">
                <span class="badge warn">
                  {{
                    Math.max(
                      0,
                      Number(s?.stockTotal ?? 0) -
                        Number(s?.stockAvailable ?? 0)
                    )
                  }}
                </span>
              </div>
            </template>
          </div>
          <span v-else>—</span>
        </el-descriptions-item>
      </el-descriptions>
      <template #footer
        ><el-button @click="detailVisible = false">关闭</el-button></template
      >
    </el-dialog>

    <el-dialog
      v-model="historyVisible"
      :title="`设备备件更换历史 - ${historyAsset?.name || ''}`"
      width="920px"
      :close-on-click-modal="false"
    >
      <!-- 筛选条 -->
      <div class="mb12" style="display: flex; gap: 10px; align-items: center">
        <el-input
          v-model="historyQuery.keyword"
          placeholder="备件名称/编码"
          clearable
          style="width: 240px"
          @keyup.enter="onDateChange()"
          @clear="onDateChange()"
        />

        <el-date-picker
          v-model="historyQuery.dateDay"
          type="date"
          placeholder="创建日期"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          @change="onDateChange()"
        />

        <!-- <SingleDateRange
          v-model="historyQuery.dateRangeDay"
          @normalized="onNorm"
          @change="onDateChange"
          placeholder="创建日期范围选择"
          style="width: 400px"
        /> -->

        <!-- <SingleDateTimeRange
          v-model="historyQuery.dateRangeSecond"
          @normalized="onNormSecond"
          @change="onDateChange"
          placeholder="维护日期时间范围选择"
        /> -->

        <!-- <DateRangePicker
          v-model="historyQuery.dateRangeDay"
          :placeholder="['起始时间', '截止时间']"
          :shortcuts="[
            {
              text: '本月',
              value: () => {
                const now = new Date();
                const s = new Date(now.getFullYear(), now.getMonth(), 1);
                const e = new Date(now.getFullYear(), now.getMonth() + 1, 0);
                return [s, e];
              },
            },
          ]"
          @normalized="onNorm"
          @change="onDateChange"
        /> -->

        <!-- <DateTimeRangePicker v-model="historyQuery.dateRangeSecond" @change="onDateTimeChange" /> -->

        <!-- <el-button type="primary" @click="fetchHistory">查询</el-button> -->
        <el-button type="primary" @click="resetQuery">重置</el-button>
        <!-- <div style="flex: 1"></div>
        <el-tag type="info" effect="text"
          >设备：{{ historyAsset?.name }}（{{
            historyAsset?.code || "无编码"
          }}）</el-tag > -->
      </div>

      <el-table
        :data="historyRows"
        v-loading="historyLoading"
        border
        stripe
        size="small"
        style="height: 400px"
        @sort-change="handleSortChange"
      >
        <!-- <el-table-column prop="createTime" label="更换时间" width="160" align="center"> 
          <template #default="{ row }">
            {{ parseTime(row.createTime, '{y}-{m}-{d}') }}
          </template>
        </el-table-column> -->

        <el-table-column
          prop="createTime"
          label="创建时间"
          width="180"
          sortable="custom"
        >
          <template #default="{ row }">
            <el-tooltip
              v-if="row.createTime"
              effect="dark"
              :content="parseTime(row.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
              placement="top"
            >
              <span>{{ parseTime(row.createTime, "{y}-{m}-{d}") }}</span>
            </el-tooltip>
            <span v-else>-</span>
          </template>
        </el-table-column>

        <el-table-column
          prop="spareName"
          label="备件名称"
          min-width="120"
          show-overflow-tooltip
        />

        <el-table-column
          prop="qty"
          label="更换数量"
          width="80"
          align="center"
        />
        <el-table-column
          prop="spareStockTotal"
          label="总库存"
          width="90"
          align="center"
        />
        <el-table-column
          prop="spareStockAvailable"
          label="当前库存"
          width="100"
          align="center"
        />
        <el-table-column prop="" label="使用数量" width="160" align="center">
          <template #default="{ row }">
            {{ row.spareStockTotal - row.spareStockAvailable }}
          </template>
        </el-table-column>
        <!-- <el-table-column
          prop="remark"
          label="备注"
          min-width="160"
          show-overflow-tooltip
        /> -->
        <el-table-column
          prop="createBy"
          label="更换人"
          width="100"
          align="center"
        />
      </el-table>

      <template #footer>
        <el-button @click="historyVisible = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="editVisible"
      v-loading="editLoading"
      :title="editForm.id ? '修改节点' : '新增子节点'"
      width="1300px"
      :close-on-click-modal="false"
    >
      <el-form
        ref="editRef"
        :model="editForm"
        :rules="editRules"
        label-width="96px"
      >
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="当前父节点">
              <el-input
                :model-value="parentPathText(editForm.parentId)"
                disabled
              />
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item label="设备类型" prop="nodeType">
              <el-select-v2
                v-model="editForm.nodeType"
                :options="typeOptions"
                placeholder="请选择设备类型"
                clearable
                filterable
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备编码" prop="code">
              <el-input
                v-model="editForm.code"
                placeholder="必填"
                @input="onCodeInput"
                @blur="checkBeTypeFirstData(editForm.code, editForm)"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备名称" prop="name">
              <el-input v-model="editForm.name" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="归属部门" prop="deptId">
              <el-tree-select
                v-model="editForm.deptId"
                :data="deptOptions"
                :props="{ value: 'id', label: 'label', children: 'children' }"
                value-key="id"
                placeholder="请选择归属部门"
                check-strictly
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备类别">
              <el-input v-model="editForm.beType" placeholder="" 
              @input="onBeTypeInput(editForm, $event)"
              @blur="checkBeTypeFirstData(editForm.beType, editForm)"/>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备区域">
              <el-input v-model="editForm.area" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备模块">
              <el-input v-model="editForm.mokuai" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设计温度">
              <el-input v-model="editForm.designTemp" placeholder="(单位:℃ )" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设计压力">
              <el-input-number
                v-model="editForm.designPress"
                placeholder="(填数值 单位:MPa )"
                class="input-400"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="电机功率">
              <el-input-number
                v-model="editForm.motorPower"
                placeholder="填数值 (单位:KW )"
                class="input-400"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="搅拌形式">
              <el-input v-model="editForm.mixingType" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备单位">
              <el-input v-model="editForm.unit" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备数量">
              <el-input-number
                v-model="editForm.quantity"
                placeholder=""
                class="input-400"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备金额">
              <el-input-number
                v-model="editForm.amount"
                placeholder="填数值 (单位:万 )"
                class="input-400"
              />
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item label="特种设备">
              <el-select
                v-model.number="editForm.isSpecial"
                placeholder="请选择类型"
                clearable
              >
                <el-option label="是" :value="1" />
                <el-option label="否" :value="0" />
              </el-select>
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item label="设备规格" prop="specification">
              <el-input
                v-model="editForm.specification"
                maxlength="100"
                show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备型号" prop="model">
              <el-input
                v-model="editForm.model"
                maxlength="100"
                show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备材质" prop="material">
              <el-input
                v-model="editForm.material"
                maxlength="60"
                show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备品牌" prop="brand">
              <el-input
                v-model="editForm.brand"
                maxlength="60"
                show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="制造厂家" prop="manufacturer">
              <el-input
                v-model="editForm.manufacturer"
                maxlength="120"
                show-word-limit
              />
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item label="设备状态" prop="status">
              <el-select
                v-model.number="editForm.status"
                placeholder="请选择设备状态"
              >
                <el-option
                  v-for="dict in asset_node_status"
                  :key="dict.value"
                  :label="dict.label"
                  :value="Number(dict.value)"
                ></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="备注">
              <el-input v-model="editForm.remark" type="textarea" rows="2" />
            </el-form-item>
          </el-col>

          <el-col :span="24">
            
            <el-form-item label="备件信息">
              <div class="spare-table-wrapper">
                <el-table
                  :data="editForm.spareRelations"
                  border
                  style="width: 100%"
                  size="small"
                  header-cell-class-name="spare-header"
                  :max-height="200"
                >
                  <!-- 备件 -->
                  <el-table-column label="备件名称" min-width="25%">
                    <template #default="{ row }">
                      <el-select-v2
                        v-model="row.spareId"
                        :options="spareListOptions"
                        placeholder="请选择备件"
                        filterable
                        clearable
                        style="width: 100%"
                        @change="onSparePicked(row)"
                      />
                    </template>
                  </el-table-column>

                  <!-- 厂商 -->
                  <el-table-column label="厂商 (vendor)" min-width="25%">
                    <template #default="{ row }">
                      <el-select
                        v-model="row.vendor"
                        placeholder="请选择厂商"
                        filterable
                        clearable
                        style="width: 100%"
                        :disabled="!row.spareId"
                        :loading="row.loadingVendor"
                        @change="onVendorPicked(row)"
                      >
                        <el-option
                          v-for="v in row.vendorOptions || []"
                          :key="v.value"
                          :label="v.label"
                          :value="v.value"
                        />
                      </el-select>
                    </template>
                  </el-table-column>

                  <!-- 规格 -->
                  <el-table-column label="规格 (spec)" min-width="25%">
                    <template #default="{ row }">
                      <el-select
                        v-model="row.spec"
                        placeholder="请选择规格"
                        filterable
                        clearable
                        style="width: 100%"
                        :disabled="!row.vendor"
                        :loading="row.loadingSpec"
                      >
                        <el-option
                          v-for="s in row.specOptions || []"
                          :key="s.value"
                          :label="s.label"
                          :value="s.value"
                        />
                      </el-select>
                    </template>
                  </el-table-column>

                  <!-- 操作 -->
                  <el-table-column label="操作" align="center" min-width="20%">
                    <template #default="{ $index }">
                      <el-button
                        link
                        type="primary"
                        @click="copySpareRow($index)"
                        >复制</el-button
                      >
                      <el-button
                        link
                        type="danger"
                        @click="removeSpareRow($index)"
                        >删除</el-button
                      >
                    </template>
                  </el-table-column>
                </el-table>

                <div class="spare-add-btn">
                  <el-button type="success" size="small" @click="addSpareRow">
                    ＋ 新增备件组
                  </el-button>
                </div> 
                <!-- <div class="spare-add-btn" v-if="Number(editForm.beTypeFirst) === 1">
                  <el-button
                    type="success"
                    size="small"
                    plain
                    @click="openAddSpare"
                  >
                    ＋ 自定义备件
                  </el-button> 
                </div> -->
                <div class="spare-add-btn" v-if="editForm.showAddSpare">
                  <el-button
                    type="primary"
                    size="small"
                    @click="openAddSpare"
                  >
                    + 自定义备件
                  </el-button>
                </div>

                
              </div>
              
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>

      <template #footer>
        <el-button @click="editVisible = false">取消</el-button>
        <el-button type="primary" @click="submitEdit">保存</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="childrenVisible"
      :title="motherCurrent?.name || '设备详情'"
      width="95%"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :destroy-on-close="true"
      @open="activeTab = 'base'"
      @closed="activeTab = 'base'"
    >
      <FeatureTabsContainer
        v-model="activeTab"
        :panes="panesForDialog"
        :keepAlive="false"
        style="--ft-primary: #3e8cf0"
        :content-max-height="660"
        v-loading="dialogLoading"
        element-loading-text="正在加载组成与明细…"
        element-loading-background="rgba(255,255,255,.6)"
      />
    </el-dialog>

    <AddPlanDrawer
      v-model="addPlanVisible"
      :asset="addPlanAsset"
      @success="onPlanCreated"
    />
    <!-- style="background-color:#accdfc" -->

    <AddSpareDialog ref="addEditDialog" @confirm="onAddSpareConfirm" />
    <el-dialog
      v-model="childrenVisible22"
      :title="motherCurrent?.name || '设备详情'"
      width="1200px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
    >
      <AssetTreeTable
        ref="treeTableRef"
        :startParentId="editParentId"
        width="100%"
        height="300px"
        :leftSpan="6"
        :tableHeight="560"
        :showSearch="false"
        :pageSizeDefault="20"
        @node-click="onNodeClick"
        @row-dblclick="onRowDblclick"
        @loaded="onLoaded"
      >
      </AssetTreeTable>
    </el-dialog>
  </div>
</template>

<script setup>
import {
  ref,
  reactive,
  computed,
  onMounted,
  nextTick,
  getCurrentInstance,
  watch,
} from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  Search,
  Refresh,
  Download,
  Close,
  Plus,
  CopyDocument,
  Setting,
  Edit,
  Tools,
  Cpu,
  Timer,
} from "@element-plus/icons-vue";
import {
  listSbtz,
  listParentId,
  getSbtz,
  delSbtz,
  cascaderChildren,
  createRoute,
  rehomeRoute,
  listSearchData,
} from "@/api/sbtz/sbtz";
import { listSbtype } from "@/api/sbtz/sbtype";
import { loadAllParams } from "@/api/page";
import { listMtplanSpareHistory } from "@/api/sbtz/mtplan";
import { listOrderSpareHistory } from "@/api/sbtz/order";
import AssetPathWizardOnly from "@/components/sbtz/AssetPathWizardOnly.vue";
import TopStripTabs from "@/components/sbtz/dao/TopStripTabs.vue";
import CardTabs from "@/components/sbtz/dao/CardTabs.vue";
// 你的模块（建议各自封装为独立 SFC）
import BaseInfo from "@/components/sbtz/dao/BaseInfo.vue";
import Composition from "@/components/sbtz/dao/Composition.vue";
import Usage from "@/components/sbtz/dao/Usage.vue";
import UsageData from "@/components/sbtz/dao/UsageData.vue";
import Maintenance from "@/components/sbtz/dao/Maintenance.vue";
import AssetTreeTable from "@/components/sbtz/AssetTreeTable.vue";
import ThRemoteSelect from "@/components/sbtz/remote/ThRemoteSelect.vue";
import {
  listSpares,
  listSpare,
  getVendorsBySpare,
  getSpecsBySpareVendor,
} from "@/api/sbtz/spare";

import FeatureSwitch from "@/components/sbtz/single/FeatureSwitch.vue";
import ConfigPage from "@/components/sbtz/single/ConfigPage.vue";

import FeatureTabsContainer from "@/components/sbtz/single/FeatureTabsContainer.vue";
import CompositionTreePane from "@/components/sbtz/single/panes/CompositionTreePane.vue";

import KeyValueEditor from "@/components/sbtz/single/panes/KeyValueEditor.vue";
import EquipmentDataDemo from "@/components/sbtz/single/panes/EquipmentDataDemo.vue";
import EquipmentUsageDemo from "@/components/sbtz/single/panes/EquipmentUsageDemo.vue";
import WorkOrder from "@/components/sbtz/single/panes/WorkOrder.vue";
import PlanWeihuAssetPane from "@/components/sbtz/single/panes/PlanWeihuAssetPane.vue";
import AddPlanDrawer from "@/components/sbtz/plan/AddPlanDrawer.vue";

import ChildCardsGrid from "@/components/sbtz/single/panes/ChildCardsGrid.vue";
import { deptTreeSelect } from "@/api/system/user";
import { status } from "nprogress";
import ExcelUploader from "@/components/sbtz/file/ExcelUploader.vue";
import dayjs from "dayjs";

const BaseFormPane = defineAsyncComponent(() =>
  import("@/components/sbtz/single/panes/BaseFormPane.vue")
);
const MailFormPane = defineAsyncComponent(() =>
  import("@/components/sbtz/single/panes/MailFormPane.vue")
);

import MachineAddDialog from "@/components/sbtz/node/MachineAddDialog.vue"; 
import AddSpareDialog from "@/components/sbtz/spare/AddSpareDialog.vue";
import { checkBeTypeFirst } from "@/api/sbtz/sbtz"; // 确保已导入
 

const addEditDialog = ref(null);
const machineAddRef = ref(null);
function openDialog() {
  machineAddRef.value.open();
}
function loadList() {
  console.log("刷新母设备列表");
}

const editLoading = ref(false);
const rootPath = ref(null);
const cascaderKey = ref(0);
const showCascader = ref(true);
const isEdit = computed(() => !!editForm.id);

// 本地简单缓存，减少请求
const vendorCache = reactive(new Map()); // key: spareId => string[]
const specCache = reactive(new Map()); // key: `${spareId}-${vendor}` => string[]

// 新增一行
function addSpareRow() {
  editForm.spareRelations.push({
    spareId: null,
    vendor: "",
    spec: "",
    vendorOptions: [],
    specOptions: [],
    loadingVendor: false,
    loadingSpec: false,
  });
}

// 删除一行
function removeSpareRow(i) {
  editForm.spareRelations.splice(i, 1);
}

// 复制一行
function copySpareRow(i) {
  const r = editForm.spareRelations[i];
  editForm.spareRelations.splice(i + 1, 0, {
    ...JSON.parse(JSON.stringify(r)),
    loadingVendor: false,
    loadingSpec: false,
  });
}

// 选中备件 → 拉取厂商，清空后续
async function onSparePicked(row) {
  row.vendor = "";
  row.spec = "";
  row.specOptions = [];
  row.vendorOptions = [];
  if (!row.spareId) return;

  try {
    row.loadingVendor = true;

    // 缓存逻辑
    if (vendorCache.has(row.spareId)) {
      row.vendorOptions = vendorCache.get(row.spareId);
    } else {
      const res = await getVendorsBySpare(row.spareId);
      vendorCache.set(row.spareId, res);
      row.vendorOptions = res;
    }
  } finally {
    row.loadingVendor = false;
  }
}

// 选中厂商 → 拉取规格
async function onVendorPicked(row) {
  console.log(row);
  row.spec = "";
  row.specOptions = [];
  if (!row.spareId || !row.vendor) return;

  const key = `${row.spareId}-${row.vendor}`;
  try {
    row.loadingSpec = true;
    if (specCache.has(key)) {
      row.specOptions = specCache.get(key);
    } else {
      const res = await getSpecsBySpareVendor(row.spareId, row.vendor);
      specCache.set(key, res);
      row.specOptions = res;
    }
  } finally {
    row.loadingSpec = false;
  }
}

// —— 把 descendants + motherCurrent 拼树（父页会自动响应更新）——
const deviceTree = computed(() =>
  buildDeviceTree(motherCurrent.value, descendants.value)
);
const detailMapObj = computed(() => {
  const obj = {};
  idMap.value.forEach((row, id) => (obj[id] = row));
  return obj;
});

const activeTab = ref("base");
const deptOptions = ref(null);

const addPlanVisible = ref(false);
const addPlanAsset = ref(null);

// 只要 ++ 就能通知子面板重新拉列表
const planReloadTick = ref(0);
const dblBusy = ref(false); // 双击总开关，给表格加遮罩
const dialogLoading = ref(false); // 弹窗内部 loading

function openAddPlan(row) {
  addPlanAsset.value = row;
  addPlanVisible.value = true;
}

function onPlanCreated() {
  planReloadTick.value++; // 新增成功 -> 通知子面板刷新
}

function openAddSpare() { 
  addEditDialog.value?.open?.();
}


function onBeTypeInput(item, val) {
  const inputEl = event?.target || document.activeElement;
  const raw = val || "";
  const filtered = raw.toUpperCase().replace(/[^A-Z0-9_-]/g, "");
  item.beType = filtered;

  if (inputEl) {
    const start = inputEl.selectionStart;
    const end = inputEl.selectionEnd;
    requestAnimationFrame(() => {
      try {
        inputEl.setSelectionRange(start, end);
      } catch {}
    });
  }

  // ✅ 输入时实时检测
  checkBeTypeFirstData(item.beType, item);
}


async function checkBeTypeFirstData(beType, item) { 
  // 允许从 code 传进来
  if (beType && beType.includes("-")) {
    const prefix = beType.split("-")[0];
    beType = prefix;
  }

  item.beType = beType;

  if (moType.value == 2) {
    return;
  }

  // 若没有类别或编码，直接不显示
  if (!beType) {
    item.showAddSpare = false;
    return;
  }

  try {
    // ✅ 同时传 beType 和 code
    const res = await checkBeTypeFirst({
      beType
    });

    console.log("checkBeTypeFirst 返回：", res);

    // 根据后端返回判断
    const exists = res?.data?.exists === true;

    // true = 该类别已有首台 → 不显示
    // false = 当前是首台 → 显示
    item.showAddSpare = !exists;
  } catch (err) {
    console.warn("checkBeTypeFirst 出错：", err);
    item.showAddSpare = false;
  }
}



// 禁用部门目录
const deptMap = new Map();
function getDeptTree() {
  deptTreeSelect().then((response) => {
    deptMap.clear();
    deptOptions.value = buildDeptTree(response.data || []);
  });
}
function buildDeptTree(nodes = []) {
  return nodes.map((n) => {
    const children = buildDeptTree(n.children || []);
    const node = {
      ...n,
      children,
      // 规则：有 children 的视为目录/公司层，禁用选择；叶子可选
      disabled: children.length > 0,
    };
    deptMap.set(n.id, node);
    return node;
  });
}
// 禁用部门目录 end

function onDateChange() {
  // val 是归整过的 ['2025-09-01 00:00:00','2025-09-24 23:59:59']
  historyQuery.pageNum = 1;
  fetchHistory();
}

// 1) 母设备名称（MACHINE by name）
const fetchMuName = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    nodeType: "MACHINE",
    name: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.name,
    id: r.id,
    text: r.name,
  }));
};

// 2) 母设备编码（MACHINE by code）
const fetchMuCode = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    nodeType: "MACHINE",
    code: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.code,
    id: r.id,
    text: r.code,
  }));
};

// 3) 系统名称（根层 parentId=0 by name）
const fetchSystemName = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0, // 根层系统
    name: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.name, // 下拉显示
    id: r.id,
    text: r.name,
  }));
};

// 4) 系统编码（根层 parentId=0 by code）
const fetchSystemCode = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0,
    code: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.code,
    id: r.id,
    text: r.code,
  }));
};

// 小工具：把 change 的返回值统一成 { text, id }
const normalizeSelectChange = (val) => {
  if (typeof val === "string") return { text: val.trim(), id: null };
  return {
    text: val?.text ?? val?.label ?? "",
    id: val?.id ?? val?.value ?? null,
  };
};

const panesForDialog = [
  {
    key: "base",
    label: "设备基本信息",
    component: BaseFormPane,
    // load: () => api.getBaseCfg(),         // 首次进入时加载一次
    // BaseFormPane 使用 v-model="modelValue" + @save，自然对接
    props: () => ({
      row: motherCurrent.value,
    }),
  },
  {
    key: "group",
    label: "设备组成信息",
    component: CompositionTreePane,
    // 直接把树(ref)作为数据源传入容器
    data: deviceTree,
    props: (tree) => ({
      nodes: tree,
      detailMap: detailMapObj.value,
      getPath: fullPathOf,
      loadDetail: (id) =>
        getSbtz(id).then((r) => {
          console.log("[getSbtz]", id, r); // 打印整个响应
          console.log("[getSbtz.data]", r?.data); // 只看 data
          return adaptRow(r.data); // 继续原有逻辑
        }),

      // 复用父级已有逻辑
      onAdd: (row) => openAddChild(row),
      onEdit: (row) => openEdit(row),
      onRemove: (row) => removeNode(row),
      onHistory: (row) => getHistory(row),
      onRefresh: () => loadAllDescendants(motherCurrent.value?.id),
    }),
  },
  {
    key: "quick",
    label: "设备使用历史",
    component: EquipmentUsageDemo,
    props: () => ({
      row: motherCurrent.value,
      // items: usageItems.value,
    }),
  },
  {
    key: "append",
    label: "设备使用数据",
    component: EquipmentDataDemo,
    props: () => ({
      row: motherCurrent.value,
      items: usageItems.value,
    }),
  },
  {
    // key:'weihu',
    // label:'设备维护维修数据',
    // component: KeyValueEditor,
    // data: [ { key:'示例A', value:'/a' } ],
    key: "weihu",
    label: "设备维护维修数据",
    component: WorkOrder,
    props: () => ({
      row: motherCurrent.value,
    }),
  },
  {
    key: "plan",
    label: "计划维护维修",
    component: PlanWeihuAssetPane,
    props: () => ({
      row: motherCurrent.value,
      onAddPlan: openAddPlan,
      refreshTick: planReloadTick.value,
    }),
  },

  // 你还可以把“基本信息/使用信息”等也配进来
];

const historyVisible = ref(false);
const historyLoading = ref(false);
const historyRows = ref([]);
const historyTotal = ref(0);
const historyQuery = reactive({
  pageNum: 1,
  pageSize: 10,
  keyword: "",
  dateRange: null,
  orderId: null,
  orderByColumn: "createTime",
  isAsc: "desc",
  dateRangeDay: null,
  dateRangeSecond: null,
  dateDay: null,
});
const historyAsset = ref(null);

async function getHistory(row) {
  historyAsset.value = row;
  historyQuery.pageNum = 1;
  historyVisible.value = true;
  historyQuery.dateRangeDay = null;
  historyQuery.dateDay = null;
  historyQuery.dateRangeSecond = null;
  await fetchHistory();
}

// 设置日期范围为当前月
function setThisMonth() {
  const d = new Date();
  const y = d.getFullYear();
  const m = d.getMonth() + 1;
  const pad = (n) => String(n).padStart(2, "0"); // 补零函数
  historyQuery.dateRange = [
    `${y}-${pad(m)}-01 00:00:00`, // 本月开始
    `${y}-${pad(m)}-${pad(new Date(y, m, 0).getDate())} 23:59:59`, // 本月结束
  ];
}

// 获取历史数据
// 可选：把空值清掉，避免传一堆 null/undefined/空串
function cleanParams(obj) {
  const out = {};
  Object.keys(obj).forEach((k) => {
    const v = obj[k];
    if (v !== null && v !== undefined && v !== "") out[k] = v;
  });
  return out;
}

async function fetchHistory() {
  const id = historyAsset?.value?.id;
  if (!id) {
    historyRows.value = [];
    historyTotal.value = 0;
    return;
  }

  // const range = Array.isArray(historyQuery.dateRangeDay) ? historyQuery.dateRangeDay : []
  // const startTime = range.length > 0 ? range[0] : null
  // const endTime   = range.length > 1 ? range[1] : null

  const sec = Array.isArray(historyQuery.dateRangeSecond)
    ? historyQuery.dateRangeSecond
    : null;
  const day = Array.isArray(historyQuery.dateRangeDay)
    ? historyQuery.dateRangeDay
    : null;
  const [startTime, endTime] =
    sec && sec.length === 2
      ? sec
      : day && day.length === 2
      ? [day[0] + " 00:00:00", day[1] + " 23:59:59"]
      : [null, null];

  // ✅ 单日期字段（来自 el-date-picker）
  // const day = historyQuery.dateDay ?? null;
  // const startTime = day ? dayjs(day).format("YYYY-MM-DD 00:00:00") : null;
  // const endTime = day ? dayjs(day).format("YYYY-MM-DD 23:59:59") : null;

  const rawParams = {
    assetId: id,
    pageNum: historyQuery.pageNum ?? 1,
    pageSize: historyQuery.pageSize ?? 20,
    keyword: historyQuery.keyword ?? null,
    orderByColumn: historyQuery.orderByColumn,
    isAsc: historyQuery.isAsc,
    "params[beginTime]": startTime,
    "params[endTime]": endTime,
  };

  const params = cleanParams(rawParams);

  const { rows, total } = await listOrderSpareHistory(params);
  historyRows.value = rows || [];
  historyTotal.value = Number(total || 0);
}

function onDateTimeChange(val) {
  historyQuery.pageNum = 1;
  fetchHistory();
}

function resetQuery() {
  historyQuery.keyword = "";
  historyQuery.dateRangeSecond = null; // ✅ 清空秒级
  historyQuery.dateRangeDay = null; // ✅ 清空天级
  historyQuery.dateDay = null;
  historyQuery.pageNum = 1;
  fetchHistory();
}

function handleSortChange({ prop, order }) {
  // prop 就是列字段，这里是 createTime
  // order 是 'ascending' | 'descending' | null
  historyQuery.orderByColumn = prop;
  historyQuery.isAsc = order === "ascending" ? "asc" : "desc";
  historyQuery.pageNum = 1;
  fetchHistory();
}

function mainSortChange({ prop, order }) {
  // order: 'ascending' | 'descending' | null
  q.orderByColumn = prop || null;
  q.isAsc =
    order === "ascending" ? "asc" : order === "descending" ? "desc" : null;
  q.pageNum = 1;
  runSearch(); // 或 fetchMothers()
}

// 重置查询
function resetHistoryFilter() {
  historyQuery.keyword = "";
  historyQuery.dateRange = null; // 重置日期范围
  historyQuery.pageNum = 1;
  fetchHistory();
}

// const usageItems = computed(() => {
//   const mk = (r = {}) => ({
//     id: r.id,
//     assetName: r.name,
//     status: r.runStatus || 'active',
//     enabledAt: r.enabledAt || r.createTime || null,
//     department: r.department || r.dept || '-',
//     useRange: (r.useStart && r.useEnd) ? { start: r.useStart, end: r.useEnd } : null,
//     idleRange: (r.idleStart && r.idleEnd) ? { start: r.idleStart, end: r.idleEnd } : null,
//     stopAt: r.stopAt || r.disableTime || null,
//   })
//   const list = []
//   if (motherCurrent.value) list.push(mk(motherCurrent.value))
//   for (const r of descendants.value) list.push(mk(r))
//   return list
// })

const usageItems = computed(() => {
  const mk = (r = {}) => {
    // 如需调试，保留此行；不需要就删掉

    // 区间对象（确保 start <= end）
    const mkRange = (a, b) => {
      if (!a || !b) return null;
      const start = a <= b ? a : b;
      const end = a <= b ? b : a;
      return { start, end };
    };

    return {
      id:
        r.id ??
        r.assetId ??
        r.code ??
        crypto?.randomUUID?.() ??
        String(Math.random()),
      assetName: r.name ?? r.assetName ?? "-",
      status: r.runStatus ?? "active", // 有真实字段就替换
      enabledAt: r.enabledAt ?? r.createTime ?? null,
      department: r.department ?? r.dept ?? "-",
      useRange: mkRange(r.useStart, r.useEnd),
      idleRange: mkRange(r.idleStart, r.idleEnd),
      stopAt: r.stopAt ?? r.disableTime ?? null,
    };
  };

  const list = [];
  if (motherCurrent.value) list.push(mk(motherCurrent.value));
  (descendants.value || []).forEach((r) => list.push(mk(r)));

  // 去重（按 id，保序）
  const seen = new Set();
  return list.filter((it) => {
    if (!it) return false;
    if (seen.has(it.id)) return false;
    seen.add(it.id);
    return true;
  });
});

// 顶部导航项（key 要和 modules 对齐）
const navItems = [
  { key: "base", label: "设备基本信息" },
  { key: "group", label: "设备组成信息" },
  { key: "usage", label: "设备使用信息" },
  { key: "usageData", label: "设备使用数据" },
  { key: "maintenance", label: "设备维护维修信息" },
];

const active = ref("base"); // 当前激活项
const activeIndex = computed(() =>
  navItems.findIndex((n) => n.key === active.value)
);

// 模块映射（与 key 一一对应）
const modules = {
  base: BaseInfo,
  group: Composition,
  usage: Usage,
  usageData: UsageData,
  maintenance: Maintenance,
};

// 切换
function switchNav(key, i) {
  active.value = key;
}

// 滑块样式（等分宽度）
const sliderStyle = computed(() => {
  const count = navItems.length || 1;
  return {
    width: `calc(100% / ${count})`,
    transform: `translateX(${
      (activeIndex.value < 0 ? 0 : activeIndex.value) * 100
    }%)`,
  };
});

// 顶部状态
const total = ref(0); // ★ 总条数
const showSearch = ref(true);
/** ======== 搜索与数据（仅加载母设备） ======== */
const q = reactive({
  name: "",
  code: "",
  createDate: null,
  pageNum: 1,
  pageSize: 10,
});
const motherRows = ref([]);
const idMap = ref(new Map()); // 索引：用于 pathText / parentPathText
const childrenVisible = ref(false);
const motherCurrent = ref(null);
const childFilter = ref("");

/** 详情 / 新增编辑弹窗 */
const detailVisible = ref(false);
const detailRow = ref(null);

const editVisible = ref(false);
const editRef = ref();
const editForm = reactive({
  id: null,
  parentId: null,
  nodeType: "MODULE",
  name: "",
  code: "",
  remark: "",
  specification: "",
  model: "",
  material: "",
  power: "",
  brand: "",
  manufacturer: "",
  isSpecial: null,
  status: null,
  deptId: null,
  spareIds: [],
  spareRelations: [], // ← 多备件数组
  beTypeFirst: 0,
});

function onCodeInput(val) {
  const s = (val || "").toUpperCase().replace(/[^A-Z0-9\-_]/g, "");
  if (s !== editForm.code) editForm.code = s;
}

const editRules = {
  name: [{ required: true, message: "设备名称必填", trigger: "blur" }],
  status: [{ required: true, message: "设备状态必填", trigger: "blur" }],
  code: [{ required: true, message: "设备编码必填", trigger: "blur" }],
  nodeType: [{ required: true, message: "请选择类型", trigger: "blur" }],
  deptId: [{ required: true, message: "请选择部门", trigger: "blur" }],
  specification: [{ max: 100, message: "最多 100 字", trigger: "blur" }],
  model: [{ max: 100, message: "最多 100 字", trigger: "blur" }],
  material: [{ max: 60, message: "最多 60 字", trigger: "blur" }],
  power: [{ max: 40, message: "最多 40 字", trigger: "blur" }],
  brand: [{ max: 60, message: "最多 60 字", trigger: "blur" }],
  manufacturer: [{ max: 120, message: "最多 120 字", trigger: "blur" }],
};
const spareListOptions = ref([]);

/** 子设备（全量子孙）容器 */
const descendants = ref([]); // ★ 存放“所有子孙”的平铺列表

function refreshByParent() {
  // 顶部 AssetPathWizardOnly 发出的刷新，按需自定义
  q.pageNum = 1;
  fetchMothers();
}

function adaptRow(r = {}) {
  const spare = r.spareInfo || {};
  const list = Array.isArray(r.spareInfos)
    ? r.spareInfos
    : r.spareInfo
    ? [r.spareInfo]
    : [];

  const childCount =
    r.childCount ??
    r.childrenCount ??
    r.subCount ??
    r.childrenNum ??
    r.childNum ??
    (Array.isArray(r.children) ? r.children.length : 0) ??
    0;

  const type = r.type || r.nodeType || "MODULE";
  return {
    id: r.id,
    pid: r.parentId ?? r.pid ?? 0,
    name: r.name || "",
    code: r.code || "",
    nodeType: r.nodeType || r.type || type,
    type,
    level: r.level ?? 1,
    remark: r.remark || "",
    createTime: r.createTime || "",
    updateTime: r.updateTime || "",
    childCount,
    specification: r.specification || "",
    model: r.model || "",
    material: r.material || "",
    power: r.power || "",
    brand: r.brand || "",
    manufacturer: r.manufacturer || "",
    isSpecial: r.isSpecial ?? null,
    status: r.status ?? null,
    deptId: r.deptId || null,

    mokuai: r.mokuai || "",
    area: r.area || "",
    designTemp: r.designTemp || "",
    designPress: r.designPress || "",
    motorPower: r.motorPower || "",
    mixingType: r.mixingType || "",
    beType: r.beType || "",
    unit: r.unit ?? null,
    quantity: r.quantity ?? null,
    amount: r.amount ?? null,
    /** ✅ 加上这一行！！！ */
    beTypeFirst: r.beTypeFirst ?? 0,

    spareInfos: list.map((s) => ({
      id: s.id ?? null,
      name: s.name ?? "",
      code: s.code ?? "",
      minStock: s.minStock ?? 0,
      maxStock: s.maxStock ?? 0,
      stockTotal: s.stockTotal ?? 0,
      stockAvailable: s.stockAvailable ?? 0,
      qtyNeed: s.qtyNeed ?? 1, // 如果你关系表返回了
      isPrimary: s.isPrimary ?? 0, // 如果你返回了
      remark: s.relRemark ?? s.remark ?? "",
      relSpec: s.relSpec ?? "",
      relVendor: s.relVendor ?? "",
    })),
    children: r.children || [],
  };
}

const TYPE_LABELS = {
  SYSTEM: "系统",
  MACHINE: "母设备",
  // MODULE: "子设备",
  // COMPONENT: "部件",
  // PART: "零件",
};

/** 只在根层展示“系统”，且禁止展开（用于 nodeType==='MACHINE'） */
const onlySystemRootProps = {
  checkStrictly: true,
  emitPath: true,
  value: "id",
  label: "label",
  lazy: true,
  async lazyLoad(node, resolve) {
    const parentId = node.level === 0 ? 0 : node.value;
    try {
      const res = await cascaderChildren(parentId);
      let list = res?.data || [];
      if (node.level === 0) {
        list = list
          .filter((x) => (x.nodeType || x.node_type) === "SYSTEM")
          .map((x) => ({ ...x, leaf: true })); // 根层直接设为叶子，禁止继续展开
      } else {
        list = []; // 非根层不展示
      }
      resolve(list);
    } catch {
      resolve([]);
    }
  },
};

/** 正常懒加载（用于其它类型） */
const normalProps = {
  checkStrictly: true,
  emitPath: true,
  value: "id",
  label: "label",
  lazy: true,
  async lazyLoad(node, resolve) {
    const parentId = node.level === 0 ? 0 : node.value;
    try {
      const res = await cascaderChildren(parentId);
      resolve(res?.data || []);
    } catch {
      resolve([]);
    }
  },
};

/** 根据类型动态切换 cProps */
const cProps = computed(() =>
  editForm.nodeType === "MACHINE" && moType.value === 2
    ? onlySystemRootProps
    : normalProps
);

/** SYSTEM 时不允许清空；其余允许 */
const cascaderClearable = computed(() => editForm.nodeType !== "SYSTEM");
const cascaderPlaceholder = computed(() =>
  cascaderClearable.value ? "选择起点（根层可留空）" : "请选择起点（必选）"
);

/** 切换类型时重置选择，避免旧值与新规则不匹配 */
watch(
  () => editForm.nodeType,
  () => {
    rootPath.value = null;
  }
);

// 估算层级：父路径长度 + 层序号
const baseLevel = ref(0);
watch(editForm.parentId, () => {
  baseLevel.value = editForm.parentId?.length || 0;
});

watch(
  [() => editForm.beType, () => editForm.code],
  async ([beType, code]) => {
    if (moType.value == 1 && beType) {
      await checkBeTypeFirstData(beType, editForm);
    }
  }
);



const idNameMap = ref(new Map());
const idPidMap = ref(new Map());
const idCodeMap = ref(new Map());

function indexRows(rows = []) {
  rows.forEach((r) => {
    const id = r.id;
    const pid = r.pid ?? r.parentId ?? 0;
    idNameMap.value.set(id, r.name ?? "");
    idPidMap.value.set(id, pid);
    idCodeMap.value.set(id, r.code ?? "");
  });
}

const idPathMap = ref(new Map());

function segOf(id) {
  const name = idNameMap.value.get(id) ?? "";
  const code = idCodeMap.value.get(id) ?? "";
  return code ? `${name}(${code})` : name;
}

function computePathForId(id, seen = new Set()) {
  if (idPathMap.value.has(id)) return idPathMap.value.get(id);
  if (seen.has(id)) {
    // 防循环
    const self = segOf(id);
    idPathMap.value.set(id, self);
    return self;
  }
  seen.add(id);

  const pid = idPidMap.value.get(id);
  const self = segOf(id);
  if (pid && pid !== 0) {
    const p = computePathForId(pid, seen);
    const full = p ? `${p} / ${self}` : self;
    idPathMap.value.set(id, full);
    return full;
  } else {
    idPathMap.value.set(id, self);
    return self;
  }
}

function rebuildIdPathMap() {
  idPathMap.value.clear();
  for (const id of idNameMap.value.keys()) computePathForId(id);
}

function updatePathsFor(rows = []) {
  // 增量
  rows.forEach((r) => computePathForId(r.id));
}

// 对外：传 row 或 id，取完整路径
function fullPathOf(rowOrId) {
  const id = typeof rowOrId === "object" ? rowOrId.id : rowOrId;
  return idPathMap.value.get(id) || "";
}

// 自动把 Map 变成普通对象（{[id]: name}）
const id2name = computed(() => {
  const obj = {};
  for (const [k, v] of idNameMap.value) obj[k] = v ?? "";
  return obj;
});

async function fetchByParent(parentId = 0) {
  const params = {
    pageNum: q.pageNum,
    pageSize: 1000,
    parentId,
    orderByColumn: q.orderByColumn,
    isAsc: q.isAsc,
  };
  const resp = await listSbtz(params);
  const rows = (resp?.rows || []).map(adaptRow);

  indexRows(rows);

  // 注意：返回 plain 对象，避免外部拿到 ref
  return { rows, id2name: id2name.value };
}

// 使用：父名称 = id2name[row.pid]
function parentNameOf(rowOrId) {
  const pid =
    typeof rowOrId === "object"
      ? rowOrId.pid ?? rowOrId.parentId
      : idPidMap.value.get(rowOrId);
  return pid ? (id2name.value ? id2name.value[pid] : id2name[pid]) : "";
}

async function fetchAllForMother(motherId) {
  // 例如先把母设备与其所有子孙拉全（你已有 BFS）
  const rows = await fetchAllDescendantsIncludingRoot(motherId); // 自己的函数：包含母机和全部子孙
  indexRows(rows); // 1) 写入 id->name/pid/code
  rebuildIdPathMap(); // 2) 统一生成 id->完整路径
  // 之后任何地方：
  // fullPathOf(someId)  或 fullPathOf(row)
}

async function ensureIndexedById(id) {
  if (!id || idNameMap.value.has(id)) return;
  const res = await getSbtz(id);
  if (res?.data) indexRows([adaptRow(res.data)]);
}

async function ensureAncestorsIndexedForId(id) {
  // 先确保当前节点本身在索引里（一般已在）
  await ensureIndexedById(id);

  // 沿着 pid 一路向上补齐祖先
  let cur = idPidMap.value.get(id);
  let guard = 0; // 防止意外循环
  while (cur && cur !== 0 && guard++ < 50) {
    await ensureIndexedById(cur);
    cur = idPidMap.value.get(cur);
  }
}

async function backfillAncestorsForList(rows = []) {
  for (const r of rows) {
    await ensureAncestorsIndexedForId(r.id);
  }
}

function clearIndexes() {
  // ★ 可选：避免索引长期膨胀
  idNameMap.value.clear();
  idPidMap.value.clear();
  idCodeMap.value.clear();
  idPathMap.value.clear();
}

async function fetchMothers() {
  const params = {
    pageNum: q.pageNum, // ★ 与若依分页匹配
    pageSize: q.pageSize, // ★ 与若依分页匹配
    name: q.name || null,
    code: q.code || null,
    createTime: q.createDate || null,
    nodeType: "MACHINE",
    orderByColumn: q.orderByColumn,
    isAsc: q.isAsc,
  };
  const resp = await listSbtz(params);
  const rows = (resp?.rows || []).map(adaptRow);
  motherRows.value = rows;
  total.value = Number(resp?.total);
  // total.value = Number(resp?.total || rows.length);

  // （推荐）新查询/翻页时重建索引，只保留当前页 + 祖先
  clearIndexes();
  indexRows(rows);
  await backfillAncestorsForList(rows);
  rebuildIdPathMap();

  // 触发一次渲染，确保 parentName/fullPath 刷新
  motherRows.value = [...motherRows.value];
}

function parentName(rowOrId) {
  const id = typeof rowOrId === "object" ? rowOrId.id : rowOrId;
  const pid =
    (typeof rowOrId === "object"
      ? rowOrId.pid ?? rowOrId.parentId
      : idPidMap.value.get(id)) ?? 0;

  if (!pid) return "—"; // 根或无父
  return idNameMap.value.get(pid) || "—"; // 来自索引的父名称
}

function parentCode(rowOrId) {
  const id = typeof rowOrId === "object" ? rowOrId.id : rowOrId;
  const pid =
    (typeof rowOrId === "object"
      ? rowOrId.pid ?? rowOrId.parentId
      : idPidMap.value.get(id)) ?? 0;

  if (!pid) return ""; // 根层无父
  return idCodeMap.value.get(pid) || "";
}

function useNum(total, aval) {
  return total - aval;
}

async function fetchAllDescendantsIncludingRoot(rootId) {
  const seen = new Set();
  const queue = [rootId];
  const out = []; // 包含根

  while (queue.length) {
    const pid = queue.shift();
    if (seen.has(pid)) continue;
    seen.add(pid);

    if (pid === rootId) {
      // 根本身的详情（如果母设备列表里已有，可以直接用那行替代）
      const rootRow = motherRows.value.find((r) => r.id === rootId);
      if (rootRow) out.push(rootRow);
      else {
        const one = await getSbtz(rootId);
        if (one?.data) out.push(adaptRow(one.data));
      }
    }

    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    out.push(...rows);
    rows.forEach((r) => queue.push(r.id));
  }

  // 去重（保险）
  const uniq = [];
  const mark = new Set();
  for (const r of out)
    if (!mark.has(r.id)) {
      mark.add(r.id);
      uniq.push(r);
    }
  return uniq;
}

/** 可按页面动态切换父ID */
const editParentId = ref(null);

function onNodeClick(data) {}
function onRowDblclick(row) {}
function onLoaded(info) {}

async function openChildrenDialog(row) {
  if (dblBusy.value) return;
  dblBusy.value = true;
  try {
    motherCurrent.value = row;

    // ✅ 重置真正绑定到 FeatureTabsContainer 的那个 ref
    activeTab.value = "base";

    childrenVisible.value = true;
    dialogLoading.value = true;
    await loadAllDescendants(row.id);
  } catch (e) {
    ElMessage.error("加载失败，请重试");
  } finally {
    dialogLoading.value = false;
    dblBusy.value = false;
  }
}

// ===== 弹窗（目录页 + 顶部导航）=====
const activeView = ref("home"); // 先显示目录
// const active     = ref<'base'|'group'|'usage'|'usageData'|'maintenance'>('base');

const parentPath = ref([]); // 级联 v-model（数组）
watch(parentPath, (arr) => {
  editForm.parentId =
    Array.isArray(arr) && arr.length ? arr[arr.length - 1] : 0; // 同步成后端要的父ID
});

function rebuildIndex(list) {
  const m = new Map();
  for (const r of list) m.set(r.id, r);
  idMap.value = m;
}

function pathText(row) {
  if (!row) return "";
  const parts = [];
  let cur = row;
  while (cur) {
    parts.unshift(`${cur.name ?? ""}(${cur.code ?? ""})`);
    if (cur.pid == null) break;
    cur = idMap.value.get(cur.pid);
  }
  return parts.join(" / ");
}

const rowClassName = () => "";

// 为空判断：null / undefined / "" / "   "
const isBlank = (v) =>
  v === null || v === undefined || (typeof v === "string" && v.trim() === "");

// 为空则返回 "-"
const fmt = (v, dash = "-") => (isBlank(v) ? dash : v);
// 带单位显示：有值才拼单位，否则返回 "-"
const fmtUnit = (v, unit, dash = "-") => (isBlank(v) ? dash : `${v}${unit}`);

function openDetail(row) {
  detailRow.value = row;
  detailVisible.value = true;
}

let moType = ref(0);
/** 新增子设备 / 修改 */
async function openAddChild(row) {
  moType.value = 1;
  editVisible.value = true;

  // 重置状态（公用函数）
  resetEditForm({
    parentId: row?.id ?? null,
    nodeType: addLabelByType(row.nodeType),
    beTypeFirst: row.beTypeFirst ?? 0,
  });

  // 刷新级联
  cascaderKey.value++;
  showCascader.value = false;
  await nextTick();
  showCascader.value = true;

  // 加载可选备件列表
  await getSpareList(); 
}

async function openEdit(row) {
  moType.value = 2;
  editVisible.value = true;
  editLoading.value = true; 

  resetEditForm({
    id: row.id ?? null,
    parentId: row.pid ?? 0,
    nodeType: row.nodeType || "MODULE",
    name: row.name || "",
    code: row.code || "",
    remark: row.remark || "",
    specification: row.specification || "",
    model: row.model || "",
    material: row.material || "",
    power: row.power || "",
    brand: row.brand || "",
    manufacturer: row.manufacturer || "",
    isSpecial: row.isSpecial ?? null,
    status: row.status ?? null,
    deptId: row.deptId || null,
    mokuai: row.mokuai || "",
    area: row.area || "",
    designTemp: row.designTemp || "",
    designPress: row.designPress || "",
    motorPower: row.motorPower || "",
    mixingType: row.mixingType || "",
    beType: row.beType ?? "",
    unit: row.unit ?? "台",
    quantity: row.quantity || 1,
    amount: row.amount || null,
    beTypeFirst: row.beTypeFirst ?? 0,
  });

  
  cascaderKey.value++;
  showCascader.value = false;
  await nextTick();
  showCascader.value = true;

  // ✅ 加载备件数据
  await getSpareList();

  editForm.showAddSpare = row.beTypeFirst == 1;
  // 初始化 spareRelations
  editForm.spareRelations = (row.spareInfos || []).map((s) => ({
    spareId: s.spareId ?? s.id,
    vendor: s.relVendor || "",
    spec: s.relSpec || "",
    vendorOptions: [],
    specOptions: [],
  }));

  // 异步加载厂商+规格
  for (const r of editForm.spareRelations) {
    const vRes = await getVendorsBySpare(r.spareId);
    r.vendorOptions = (vRes?.data || []).map((v) => ({
      label: v.label ?? v.value ?? v,
      value: v.value ?? v.label ?? v,
    }));

    if (r.vendor) {
      const sRes = await getSpecsBySpareVendor(r.spareId, r.vendor);
      r.specOptions = (sRes?.data || []).map((s) => ({
        label: s.label ?? s.value ?? s,
        value: s.value ?? s.label ?? s,
      }));
    }
  }

  // 获取详情
  const detail = await getSbtz(row.id)
    .then((r) => r?.data)
    .catch(() => null);
  const ids = (detail?.spareInfos || [])
    .map((s) => Number(s.id))
    .filter((v) => Number.isFinite(v));

  editForm.spareIds = ids;

  if (editForm.id) await buildBanIdsFor(editForm.id);
  else banIds.value = new Set();

  if (editForm.parentId) await ensureAncestorsIndexedForId(editForm.parentId);
  parentPath.value = await buildCascaderPath(editForm.parentId);

  editLoading.value = false;
}

function resetEditForm(defaults = {}) {
  Object.assign(editForm, {
    id: null,
    parentId: null,
    nodeType: "MODULE",
    name: "",
    code: "",
    remark: "",
    specification: "",
    model: "",
    material: "",
    power: "",
    brand: "",
    manufacturer: "",
    isSpecial: null,
    status: 0,
    deptId: null,
    mokuai: "",
    area: "",
    designTemp: "",
    designPress: "",
    motorPower: "",
    mixingType: "",
    beType: "",
    unit: "台",
    quantity: 1,
    amount: null,
    beTypeFirst: 0,
    spareIds: [],
    spareRelations: [],
    ...defaults, // ✅ 允许个别字段由外部覆盖
  });
  nextTick(() => editRef.value?.clearValidate());
}

async function buildCascaderPath(targetId) {
  if (!targetId) return [];
  // 生成从根到目标的 id 链
  const chain = [];
  let cur = targetId,
    guard = 0;
  while (cur && guard++ < 100) {
    chain.unshift(cur);
    cur = idPidMap.value.get(cur) ?? 0;
  }
  // 让级联在懒加载模式下也能吃到链（无需手动展开，ElementPlus 会逐级触发 lazyLoad）
  return chain;
}

async function submitEdit() {
  try {
    editLoading.value = true; // 开启 loading
    await editRef.value.validate();

    if (editForm.id) {
      // 修改
      const payload = {
        nodeId: editForm.id,
        newParentId: editForm.parentId ?? 0,
        keepCode: false,
        override: {
          nodeType: editForm.nodeType,
          name: editForm.name,
          code: editForm.code ?? null,
          remark: editForm.remark || "",

          // 新增字段
          specification: editForm.specification || "",
          model: editForm.model || "",
          material: editForm.material || "",
          power: editForm.power || "",
          brand: editForm.brand || "",
          manufacturer: editForm.manufacturer || "",
          isSpecial: editForm.isSpecial ?? null,
          status: editForm.status ?? null,
          deptId: editForm.deptId || null,
          spareIds: Array.isArray(editForm.spareIds) ? editForm.spareIds : [],

          mokuai: editForm.mokuai || "",
          area: editForm.area || "",
          designTemp: editForm.designTemp || "",
          designPress: editForm.designPress || "",
          motorPower: editForm.motorPower || "",
          mixingType: editForm.mixingType || "",
          beType: editForm.beType || "",
          unit: editForm.unit || "台",
          quantity: editForm.quantity ?? null,
          amount: editForm.amount ?? null,

          spareRelations: editForm.spareRelations
            .filter((r) => r.spareId)
            .map((r) => ({
              spareId: r.spareId,
              vendor: r.vendor || "",
              spec: r.spec || "",
              qtyNeed: r.qtyNeed ?? 1, // 默认1，可选
              isPrimary: r.isPrimary ?? 0, // 默认0，可选
              remark: r.remark || "",
            })),
        },
      };
      const resData = await rehomeRoute(payload);
      if (resData.code == 200) {
        ElMessage.success("修改成功");
      } else {
        ElMessage.success("修改失败");
      }

      fetchMothers();
      // 刷新弹窗里的“所有子孙”
      if (motherCurrent.value) await loadAllDescendants(motherCurrent.value.id);
      editVisible.value = false;
    } else {
      // 新增子节点（子设备）
      console.log("editForm");
      console.log(editForm);
      const layer = {
        assetId: editForm.parentId,
        nodeType: editForm.nodeType,
        name: editForm.name,
        code: editForm.code ?? null,
        remark: editForm.remark || "",

        // 新增字段
        specification: editForm.specification || "",
        model: editForm.model || "",
        material: editForm.material || "",
        power: editForm.power || "",
        brand: editForm.brand || "",
        manufacturer: editForm.manufacturer || "",
        isSpecial: editForm.isSpecial ?? null,
        status: editForm.status ?? null,
        deptId: editForm.deptId || null,
        spareIds: Array.isArray(editForm.spareIds) ? editForm.spareIds : [],
        mokuai: editForm.mokuai || "",
        area: editForm.area || "",
        designTemp: editForm.designTemp || "",
        designPress: editForm.designPress || "",
        motorPower: editForm.motorPower || "",
        mixingType: editForm.mixingType || "",
        beType: editForm.beType || "",
        unit: editForm.unit || "台",
        quantity: editForm.quantity ?? null,
        amount: editForm.amount ?? null,

        spareRelations: editForm.spareRelations
          .filter((r) => r.spareId)
          .map((r) => ({
            spareId: r.spareId,
            vendor: r.vendor || "",
            spec: r.spec || "",
            qtyNeed: r.qtyNeed ?? 1, // 默认1，可选
            isPrimary: r.isPrimary ?? 0, // 默认0，可选
            remark: r.remark || "",
          })),
      };
      const payload = {
        startParentId: editForm.parentId,
        reuseCode: false,
        layers: [layer],
      };
      const res = await createRoute(payload);
      if (res.code == 200) {
        ElMessage.success("新增成功");
      } else {
        ElMessage.error("新增失败");
      }

      if (motherCurrent.value) await loadAllDescendants(motherCurrent.value.id);
      editVisible.value = false;
    }
  } catch (e) {
    // 校验/接口报错都会到这
    // 可按需提示：ElMessage.error(e?.message || "保存失败");
  } finally {
    editLoading.value = false; // 无论成功失败都关闭 loading
  }
}

/** 删除节点（母或子） */
async function removeNode(row) {
  try {
    await ElMessageBox.confirm(`确定删除【${row.name}】？`, "提示", {
      type: "warning",
    });
  } catch {
    return;
  }
  await delSbtz(row.id);
  ElMessage.success("删除成功");
  if (childrenVisible.value && motherCurrent.value) {
    await loadAllDescendants(motherCurrent.value.id);
  } else {
    await fetchMothers();
  }
}

/** label */
function labelByType(t) {
  switch ((t || "").toUpperCase()) {
    case "SYSTEM":
      return "系统";
    case "MACHINE":
      return "母设备";
    case "MODULE":
      return "子设备";
    case "COMPONENT":
      return "部件";
    case "PART":
      return "零件";
    default:
      return t || "—";
  }
}

function addLabelByType(t) {
  switch ((t || "").toUpperCase()) {
    case "SYSTEM":
      return "MACHINE";
    case "MACHINE":
      return "MODULE";
    case "MODULE":
      return "COMPONENT";
    case "COMPONENT":
      return "PART";
    case "PART":
      return "PART";
    default:
      return t || "—";
  }
}

/** ========== 父节点选择器（懒加载） ========== **/
const parentTreeData = ref([]); // 根层（parentId=0）的选项
const banIds = ref(new Set()); // 禁选集合：当前节点及其所有后代
const parentTreeProps = {
  value: "id",
  label: "label",
  children: "children",
  isLeaf: "isLeaf",
  disabled: "disabled",
  lazy: true,
  lazyLoad: loadParentNodes,
};

/** 把接口 rows 转成 TreeSelect 节点 */
function toOption(r) {
  const label = r.code ? `${r.name}(${r.code})` : r.name;
  const hasChildren =
    (r.childCount ??
      r.childrenCount ??
      r.subCount ??
      r.childrenNum ??
      r.childNum ??
      0) > 0;
  return {
    id: r.id,
    label,
    isLeaf: !hasChildren, // 没有子，就标记叶子；即便不准，也能继续懒加载
    disabled: banIds.value.has(r.id),
    children: [], // 懒加载时填充
  };
}

/** 懒加载：加载某节点的直接子 */
async function loadParentNodes(node, resolve) {
  try {
    const pid = node?.level ? node.data.id : 0; // level 为 0/undefined 代表根
    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    // 建索引（供路径显示/后续使用）
    indexRows(rows);
    updatePathsFor(rows);
    const options = rows.map(toOption);
    resolve(options);
  } catch (e) {
    resolve([]);
  }
}

/** 计算“禁选集合”：当前节点及其所有后代 */
async function buildBanIdsFor(currentId) {
  const set = new Set([currentId]);
  // BFS 拉全后代，仅拿 id 填集合
  const q = [currentId];
  while (q.length) {
    const pid = q.shift();
    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    rows.forEach((r) => {
      if (!set.has(r.id)) {
        set.add(r.id);
        q.push(r.id);
      }
    });
  }
  banIds.value = set;
}

/** 在已加载的树里找一个节点（辅助函数） */
function findNodeInTree(list, id) {
  const st = Array.isArray(list) ? [...list] : [];
  while (st.length) {
    const n = st.shift();
    if (!n) continue;
    if (n.id === id) return n;
    if (n.children?.length) st.unshift(...n.children);
  }
  return null;
}

function copyPath(row) {
  const text = fullPathOf(row);
  navigator.clipboard?.writeText(text);
  ElMessage.success("已复制路径：" + text);
}

/** ★ 拉取某母设备下的“所有后代（子孙）”：BFS 连续请求 parentId=xxx 直到叶子 */
async function loadAllDescendants(rootId) {
  const seen = new Set(); // 防重
  const queue = [rootId]; // 从母设备 id 开始
  const collected = []; // 平铺结果（不包括根节点）
  const pageSize = 1000; // 可按需调整

  while (queue.length) {
    const pid = queue.shift();
    if (seen.has(pid)) continue;
    seen.add(pid);

    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize }); // 直系子
    // const rows = (resp?.rows || []).map(adaptRow);
    const rows = resp?.rows;

    for (const r of rows) {
      idMap.value.set(r.id, r);
      // 关键：把子设备也写进 idNameMap/idPidMap，供路径计算
      indexRows([r]);
      // 增量生成该节点的完整路径（会沿着 pid 递归向上）
      computePathForId(r.id);

      if (r.id !== rootId) collected.push(r);
      queue.push(r.id);
    }
  }

  // 你也可以在这里去重/排序：按 level 再按 name
  collected.sort(
    (a, b) =>
      (a.level || 0) - (b.level || 0) ||
      String(a.name).localeCompare(String(b.name))
  );

  descendants.value = collected;

  // 可选：一次性重建，保证所有 id 都有缓存路径
  rebuildIdPathMap();
}

const { proxy } = getCurrentInstance();
const { asset_node_status } = proxy.useDict("asset_node_status");
/** 导出（维持与你项目一致的导出接口） */
function handleExport() {
  // proxy.download(
  //   "sbtz/sbtz/export",
  //   { ...q, nodeType: "MACHINE" },
  //   `sbtz_${new Date().getTime()}.xlsx`
  // );
  proxy.download(
    "sbtz/sbtz/excel",
    { ...q, nodeType: "MACHINE" },
    `sbtz_${new Date().getTime()}.xlsx`
  );
  // proxy.download(
  //   "sbtz/sbtz/exportChain",
  //   { ...q, nodeType: "MACHINE" },
  //   `mother_devices_${Date.now()}.xlsx`
  // );
}

// 顶部 import 保持不变

/** Affix 的滚动容器（字符串选择器），找不到则用 window */
const affixTarget = ref(undefined);

onMounted(() => {
  // 按你项目实际情况补充/调整候选
  const candidates = [
    ".app-main .el-scrollbar__wrap", // RuoYi + Element Plus 常见
    ".el-scrollbar__wrap",
    ".app-main",
  ];
  const found = candidates.find((sel) => document.querySelector(sel));
  // el-affix 的 target 需要字符串；undefined 则表示使用 window
  affixTarget.value = found || undefined;
});

const spareLoaded = ref(false);

/** 备件列表（用于新增/编辑） */
async function getSpareList() {
  const { rows = [] } = await listSpare({ pageNum: 1, pageSize: 9999 });
  // ✅ 统一成 Number（如果你的 id 可能很大>2^53-1，就统一用 String，两端都用 String）
  spareListOptions.value = rows.map(({ id, name }) => ({
    value: Number(id),
    label: name,
  }));
  spareLoaded.value = true;
}

function parentPathText(pid) {
  if (!pid) return "—";
  // 先尝试已有完整路径
  const cached = idPathMap.value.get(pid);
  if (cached) return cached;

  // 没有则现场计算（依赖 idNameMap/idPidMap）
  computePathForId(pid);
  return idPathMap.value.get(pid) || idNameMap.value.get(pid) || "—";
}

// —— 把母设备 + 子孙扁平列表 → 树（数组，只有一个根）——
function buildDeviceTree(rootRow, flat = []) {
  if (!rootRow) return [];
  const map = new Map();
  const mk = (r) => {
    // const label = r.code ? `${r.name}(${r.code})` : r.name;
    const label = r.name;
    return map
      .set(r.id, {
        id: r.id,
        label,
        _rawName: r.name || "",
        _rawCode: r.code || "",
        childCount: r.childCount ?? 0,
        typeLabel: labelByType(r.nodeType || r.type),
        children: [],
      })
      .get(r.id);
  };
  mk(rootRow);
  flat.forEach(mk);
  flat.forEach((r) => {
    const pid = r.pid ?? r.parentId ?? 0;
    if (pid && map.has(pid)) map.get(pid).children.push(map.get(r.id));
  });
  return [map.get(rootRow.id)];
}

const typeOptions = ref([]);
async function getTypeList() {
  const res = await listSbtype(loadAllParams);
  typeOptions.value = (res?.rows ?? []).map(
    ({ typeCode: value, displayName: label }) => ({ value, label })
  );
}

// ====== 列头筛选状态（统一管理）======
const filters = reactive({
  // 下拉+自定义：display 供 UI 绑定；text 是规范化后的文本；id 用于精确匹配父节点
  systemName: { display: null, text: "", id: null },
  systemCode: { display: null, text: "", id: null },
  muName: { display: null, text: "", id: null },
  MuCode: { display: null, text: "", id: null },
  pathKw: "",
  nameCodeKw: "",
});

// 选项池与加载态
const options = reactive({
  systemName: [],
  systemCode: [], // ★ 新增
  muName: [],
  MuCode: [],
});

const loading = reactive({
  systemName: false,
  systemCode: false, // ★ 新增
  muName: false,
  MuCode: false,
});

// 远程下拉：每列可接不同后端接口
const remoteOptions = {
  async systemName(query) {
    loading.systemName = true;
    try {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        name: query || null,
        parentId: 0,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      options.systemName = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.name || "",
        label: r.code ? `${r.name}(${r.code})` : r.name || "",
      }));
    } finally {
      loading.systemName = false;
    }
  },
  async parentCode(query) {
    loading.parentCode = true;
    try {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        code: query || null,
        parentId: 0,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      const codes = Array.from(
        new Set(rows.map((r) => r.code).filter(Boolean))
      );
      options.parentCode = codes.map((c) => ({ value: c, label: c, text: c }));
    } finally {
      loading.parentCode = false;
    }
  },
  async MuName(query) {
    loading.muName = true;
    try {
      const q = (query || "").trim();
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        nodeType: "MACHINE", // 只搜母设备
        name: q || null,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      options.muName = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.name || "",
        label: r.name,
        // label: r.code ? `${r.name}(${r.code})` : r.name || "",
      }));
    } finally {
      loading.muName = false;
    }
  },
  async MuCode(query) {
    loading.MuCode = true;
    try {
      const q = (query || "").trim();
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        nodeType: "MACHINE", // 只搜母设备
        code: q || null,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      options.MuCode = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.code || "",
        label: r.code,
      }));
    } finally {
      loading.MuCode = false;
    }
  },
};

const SYSCODE_MIN_CHARS = 1; // 可按需设 1~2

remoteOptions.systemCode = async function (query) {
  const q = (query || "").trim();
  if (q && q.length < SYSCODE_MIN_CHARS) {
    options.systemCode = [];
    return;
  }
  loading.systemCode = true;
  try {
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 30,
      parentId: 0, // 根层系统
      code: q || null, // 按系统“编码”搜父
    });
    const rows = (resp?.rows || []).map(adaptRow);
    options.systemCode = rows.map((r) => ({
      value: r.id,
      id: r.id, // 父系统 id（后续筛选用）
      text: r.code || "",
      label: r.code,
      // label: r.code ? `${r.code}（${r.name || '-' }）` : (r.name || '-'),
    }));
  } finally {
    loading.systemCode = false;
  }
};

// ====== 远程下拉优化：系统名称 ======
const sysnameCache = new Map(); // key: queryLower -> options[]
let sysnameTimer = null;
let sysnameSeq = 0; // 用于丢弃过期请求的竞态序号

remoteOptions.systemName = function (query) {
  const q = (query || "").trim();
  // ✅ 最小输入长度限制（空字符串用于“首屏预取”）
  if (q && q.length < SYSNAME_MIN_CHARS) {
    options.systemName = [];
    return;
  }

  // ✅ 简单去抖：避免用户每敲一个字符都打一次接口
  if (sysnameTimer) clearTimeout(sysnameTimer);
  const mySeq = ++sysnameSeq;
  loading.systemName = true;

  sysnameTimer = window.setTimeout(async () => {
    try {
      const key = q.toLowerCase();

      // ✅ 命中缓存直接返回
      if (sysnameCache.has(key)) {
        if (mySeq === sysnameSeq) options.systemName = sysnameCache.get(key);
        return;
      }

      // ✅ 发请求（注意限制 pageSize）
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 30, // 控制体量，首屏更快
        parentId: 0, // 根层（你的需求）
        name: q || null,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      const opts = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.name || "",
        label: r.code ? `${r.name}(${r.code})` : r.name || "",
      }));

      // ✅ 写入缓存，后续相同关键词秒出
      sysnameCache.set(key, opts);

      // ✅ 仅当这是最新一次调用时才落地结果（丢弃过期响应）
      if (mySeq === sysnameSeq) options.systemName = opts;
    } finally {
      // 只有“自己是最新一次调用”时才关 loading，避免闪烁
      if (mySeq === sysnameSeq) loading.systemName = false;
    }
  }, 250); // 250ms 去抖（可调）
};

const SYSNAME_MIN_CHARS = 2; // ✅ 输入至少 2 个字符才发请求（可调）

// ====== 搜索模式：统一接口 或 分列接口交集 ======
const SEARCH_MODE = "federated"; // 'unified' | 'federated'

// 每列的数据源函数：返回一个“id 集合”（Set<number>）
const FILTER_SOURCES = {
  async systemName(values) {
    // 1) 选中了具体父（精确：取其直系子）
    if (values && values.id) {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: values.id,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      return new Set(rows.map((r) => r.id));
    }
    // 2) 自定义名称（按父名称模糊 → 汇总这些父的直系子）
    const text = values && values.text ? values.text.trim() : "";
    if (!text) return new Set();

    // （这里就是“把输入内容传递给后端进行搜索”的地方）
    const parentResp = await listSearchData({
      pageNum: 1,
      pageSize: 1000,
      name: text,
      nodeType: "SYSTEM",
    });
    const parents = (parentResp?.rows || []).map(adaptRow);
    if (!parents.length) return new Set();

    const childIds = new Set();
    for (const p of parents) {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: p.id,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      rows.forEach((r) => childIds.add(r.id));
    }
    return childIds;
  },
  // ★ 新增：按“父系统编码”→ 找到父ID → 汇总这些父的直系子（母设备）ID
  async systemCode(values) {
    // 精确选中项（含 id）时，等价于直接查这个父ID的直系子
    const ids = [];
    if (values && values.id) {
      ids.push(values.id);
    } else if (values && values.text) {
      // 模糊编码 → 根层父系统 id[]
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: 0,
        code: values.text.trim(),
      });
      const rows = (resp?.rows || []).map(adaptRow);
      rows.forEach((r) => ids.push(r.id));
    }
    if (!ids.length) return new Set();

    // 汇总这些父系统的直系子（母设备）
    const childIds = new Set();
    for (const pid of ids) {
      const r = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: pid,
        nodeType: "MACHINE",
      });
      const rows = (r?.rows || []).map(adaptRow);
      rows.forEach((row) => childIds.add(row.id));
    }
    return childIds;
  },
  // ✅ 母设备（只命中母设备本身，不取子）
  async muName(values) {
    // 选中具体项：返回该母设备 id
    if (values && values.id) return new Set([values.id]);

    // 自定义文本：按“母设备名称/编码”模糊命中母设备自身
    const text = values && values.text ? values.text.trim() : "";
    if (!text) return new Set();
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 1000,
      nodeType: "MACHINE",
      // 视后端而定：若 name 与 code 不能同传，可只传 name:text，
      // 或用你已有的 nameCodeLike；这里给出通用写法：
      name: text,
    });
    const rows = (resp?.rows || []).map(adaptRow);
    return new Set(rows.map((r) => r.id));
  },

  async muCode(values) {
    if (values && values.id) return new Set([values.id]); // 精确选中
    const text = values && values.text ? values.text.trim() : "";
    if (!text) return new Set();
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 1000,
      nodeType: "MACHINE",
      code: text,
    });
    const rows = (resp?.rows || []).map(adaptRow);
    return new Set(rows.map((r) => r.id));
  },
  async pathKw(text) {
    if (!text) return new Set();
    const resp = await listSbtz({ pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    indexRows(rows);
    updatePathsFor(rows);
    const kw = (text || "").toLowerCase();
    return new Set(
      rows
        .filter((r) => (fullPathOf(r) || "").toLowerCase().includes(kw))
        .map((r) => r.id)
    );
  },
  async nameCodeKw(text) {
    if (!text) return new Set();
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 1000,
      name: text,
      code: text,
    });
    const rows = (resp?.rows || []).map(adaptRow);
    return new Set(rows.map((r) => r.id));
  },
};

// 可选：没有批量详情接口时，用并发 getSbtz 兜底
async function fetchByIds(ids) {
  if (!ids || !ids.length) return [];
  const out = [];
  const pool = 10;
  for (let i = 0; i < ids.length; i += pool) {
    const slice = ids.slice(i, i + pool);
    const res = await Promise.all(
      slice.map((id) =>
        getSbtz(id)
          .then((r) => adaptRow(r.data))
          .catch(() => null)
      )
    );
    out.push(...res.filter(Boolean));
  }
  return out;
}

// 如果后端支持一次传多个父ID，请改为 true，并确认参数名（这里用 parentIds）
const SUPPORTS_PARENT_IDS = false;

// 根层父系统的模糊查，拿到 id 数组（父 name → pid[]）
async function searchParentIdsByNameLike(text) {
  const kw = (text || "").trim();
  if (!kw) return [];
  const resp = await listSearchData({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0, // 只查根层系统
    name: kw,
  });
  const rows = (resp?.rows || []).map(adaptRow);
  return rows.map((r) => r.id);
}

async function searchParentIdsByCodeLike(text) {
  const kw = (text || "").trim();
  if (!kw) return [];
  const resp = await listSearchData({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0, // 根层系统
    code: kw,
  });
  const rows = (resp?.rows || []).map(adaptRow);
  return rows.map((r) => r.id);
}

// 当后端不支持 parentIds 数组时：按每个 pid 并发请求，合并并做前端分页
async function fetchMotherByParentsFallback(parentIds, baseParams) {
  // 取足够的条数以便最终分页切片
  const need = q.pageNum * q.pageSize;
  const perPageSize = Math.max(need, 50);

  const jobs = parentIds.map((pid) =>
    listSbtz({
      ...baseParams,
      parentId: pid,
      pageNum: 1,
      pageSize: perPageSize,
    })
      .then((r) => ({
        rows: (r?.rows || []).map(adaptRow),
        total: Number(r?.total || 0),
      }))
      .catch(() => ({ rows: [], total: 0 }))
  );

  const parts = await Promise.all(jobs);

  // 合并去重
  const merged = [];
  let total = 0;
  const seen = new Set();
  for (const part of parts) {
    total += part.total;
    for (const row of part.rows) {
      if (seen.has(row.id)) continue;
      seen.add(row.id);
      merged.push(row);
    }
  }

  // 可选排序：更新时间倒序，其次名称
  merged.sort(
    (a, b) =>
      new Date(b.updateTime || 0) - new Date(a.updateTime || 0) ||
      String(a.name).localeCompare(String(b.name))
  );

  // 最终分页切片
  const start = (q.pageNum - 1) * q.pageSize;
  const end = start + q.pageSize;
  return { rows: merged.slice(start, end), total };
}

// 统一触发的搜索
async function runSearch() {
  if (SEARCH_MODE === "unified") {
    // 1) 先解析父系统条件 -> parentIds

    let parentIdsFromName = [];
    if (filters.systemName.id) {
      parentIdsFromName = [filters.systemName.id];
    } else if (filters.systemName.text) {
      parentIdsFromName = await searchParentIdsByNameLike(
        filters.systemName.text
      );
    }

    let parentIdsFromCode = [];
    if (filters.systemCode.id) {
      parentIdsFromCode = [filters.systemCode.id];
    } else if (filters.systemCode.text) {
      parentIdsFromCode = await searchParentIdsByCodeLike(
        filters.systemCode.text
      );
    }

    let parentIds = [];
    if (parentIdsFromName.length && parentIdsFromCode.length) {
      const s = new Set(parentIdsFromCode);
      parentIds = parentIdsFromName.filter((x) => s.has(x));
    } else {
      parentIds = parentIdsFromName.length
        ? parentIdsFromName
        : parentIdsFromCode;
    }

    // 2) 解析母设备条件
    const muId = filters.muName.id || null;
    const codeId = filters.MuCode.id || null;
    const muText = (filters.muName.text || "").trim();
    const muCode = (filters.MuCode.text || "").trim();
    // 3) 统一的基础参数（除父ID外的其它条件）
    const baseParams = {
      nodeType: "MACHINE",
      ...(muId
        ? { id: muId }
        : {
            name: muText || q.name || null,
            code: muCode || q.code || null,
          }),
      pathLike: filters.pathKw || null,
      nameCodeLike: filters.nameCodeKw || null,
      createTime: q.createDate || null,
    };
    // 4) 根据 parentIds 的数量选择查询策略
    if (parentIds.length === 0) {
      // 无父过滤：一次请求
      const resp = await listSearchData({
        ...baseParams,
        pageNum: q.pageNum,
        pageSize: q.pageSize,
        orderByColumn: q.orderByColumn,
        isAsc: q.isAsc,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      await afterRowsArrived(rows, resp?.total ?? rows.length);
      return;
    }

    if (parentIds.length === 1) {
      // 单个父ID：直接带 parentId
      const resp = await listSbtz({
        ...baseParams,
        parentId: parentIds[0],
        pageNum: q.pageNum,
        pageSize: q.pageSize,
        orderByColumn: q.orderByColumn,
        isAsc: q.isAsc,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      await afterRowsArrived(rows, resp?.total ?? rows.length);
      return;
    }

    // 多个父ID
    if (SUPPORTS_PARENT_IDS) {
      // ✅ 如果你的后端支持多个父ID参数（数组或逗号串），用这一段：
      const resp = await listSbtz({
        ...baseParams,
        parentIds, // 如需改为字符串：parentIds: parentIds.join(',')
        pageNum: q.pageNum,
        pageSize: q.pageSize,
        orderByColumn: q.orderByColumn,
        isAsc: q.isAsc,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      await afterRowsArrived(rows, resp?.total ?? rows.length);
      return;
    } else {
      // ❗后端不支持多个父ID：按父ID并发查并前端合并分页
      const { rows, total } = await fetchMotherByParentsFallback(
        parentIds,
        baseParams
      );
      await afterRowsArrived(rows, total);
      return;
    }
  }

  // 分列接口：各列查候选 → 交集 → 拉详情
  const jobs = [];

  // ★ 加上母设备（名称/编码）
  if (filters.muName.id || filters.muName.text) {
    jobs.push(
      FILTER_SOURCES.muName({
        id: filters.muName.id,
        text: filters.muName.text,
      })
    );
  }

  if (filters.MuCode.id || filters.MuCode.text) {
    jobs.push(
      FILTER_SOURCES.muCode({
        id: filters.MuCode.id,
        text: filters.MuCode.text,
      })
    );
  }

  if (filters.systemName.id || filters.systemName.text) {
    jobs.push(
      FILTER_SOURCES.systemName({
        id: filters.systemName.id,
        text: filters.systemName.text,
      })
    );
  }
  if (filters.systemCode.id || filters.systemCode.text) {
    jobs.push(
      FILTER_SOURCES.systemCode({
        id: filters.systemCode.id,
        text: filters.systemCode.text,
      })
    );
  }

  if (filters.pathKw) jobs.push(FILTER_SOURCES.pathKw(filters.pathKw));
  if (filters.nameCodeKw)
    jobs.push(FILTER_SOURCES.nameCodeKw(filters.nameCodeKw));

  if (!jobs.length) {
    await fetchMothers(); // 无列头过滤，回退到你的原查询（分页等保持一致）
    return;
  }

  const sets = await Promise.all(jobs);
  let inter = sets.shift() || new Set();
  for (const s of sets) inter = new Set([...inter].filter((x) => s.has(x)));

  const ids = [...inter];
  const rows = await fetchByIds(ids);
  afterRowsArrived(rows, rows.length);
}

// 统一后处理（索引/路径/刷新 UI）
async function afterRowsArrived(rows, totalLike) {
  motherRows.value = rows;
  total.value = Number(totalLike ?? rows.length);

  clearIndexes();
  indexRows(rows);
  await backfillAncestorsForList(rows);
  rebuildIdPathMap();

  motherRows.value = [...motherRows.value];
}

// 文本输入列：轻微防抖自动查
let thTimer;
function scheduleSearch() {
  if (thTimer) clearTimeout(thTimer);
  thTimer = window.setTimeout(runSearch, 300);
}

getDeptTree();
getTypeList();
onMounted(async () => {
  await getSpareList();
  // await fetchMothers();
  await runSearch();
});
</script>

<style scoped>
.mb12 {
  margin-bottom: 12px;
}
.flex {
  display: flex;
}
.items-center {
  align-items: center;
}
.cell-flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: 8px;
}
.cell-left {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.cell-left .name {
  font-weight: 600;
  /* white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis; */
}
.cell-right {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.one-line {
  display: inline-block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.children-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

/* 2 列卡片布局 */
.card-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}
@media (max-width: 720px) {
  .card-grid {
    grid-template-columns: 1fr;
  }
}
.child-card .card-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.child-card .hd-left {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  min-width: 0;
}
.child-card .card-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 420px;
}
.child-card .card-body {
  display: grid;
  gap: 6px;
}
.child-card .kv .k {
  color: #909399;
}
.child-card .kv .v {
  color: #303133;
}
.child-card .card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

/* 路径文本：默认一行，空间不够时在自身容器内换行，不影响右侧标签 */
.path-text {
  display: inline-block;
  max-width: 100%;
  /* 允许自动换行（长英文/长数字也能断） */
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
  line-height: 1.4;
}

/* 若“路径”与右侧标签同在一格横向布局时，保证标签不被挤压 */
.cell-flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: 8px;
}

/* 左侧文本区域必须允许收缩，否则会把右侧标签顶走 */
.cell-left {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0; /* ★ 关键：允许左侧文本收缩 */
}

/* 右侧标签固定宽，不被文本挤压 */
.cell-right {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0; /* ★ 关键：标签不收缩 */
}

/* 让 el-dialog 头部留白，自己画顶栏 */
.equip-dialog :deep(.el-dialog__header) {
  padding: 0;
  border-bottom: none;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 16px 0;
  background: #2f3a49;
  color: #e8eef5;
  position: sticky;
  top: 0;
  z-index: 1;
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
}

.topbar .logo {
  font-weight: 700;
  letter-spacing: 1px;
  margin-right: 8px;
  color: #fff;
}

.tabs {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 0 10px; /* 给 slider 留位置 */
  flex: 1;
}

.tab {
  appearance: none;
  border: 0;
  background: transparent;
  color: #c9d3dc;
  padding: 8px 14px;
  font-size: 14px;
  cursor: pointer;
  border-radius: 8px;
  transition: color 0.15s ease, background-color 0.15s ease;
  flex: 1 1 0; /* 均分宽度 */
  text-align: center;
}
.tab:hover {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.06);
}
.tab.active {
  color: #ffffff;
}

.slider {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  background: #8aaef5;
  border-radius: 3px;
  transition: transform 0.25s ease;
}

.content-wrap {
  background: #f5f7fa;
  padding: 16px;
}

.mb12 {
  margin-bottom: 12px;
}

/* 内容卡片：顶边与 active tab 的“白色下边线”接壤 */
.panel {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 0 8px 8px 8px;
  margin-top: -1px;
  padding: 16px;
  background-color: #accdfc;
}

.inputWidth {
  width: 220px;
}

.th-filter {
  display: grid;
  gap: 6px;
  align-items: center;
  justify-items: start;
  padding: 6px 6px 8px;
}
.th-filter > span {
  font-size: 12px;
  color: #606266;
  line-height: 1;
}
.th-filter.is-filtered > span {
  color: #3e8cf0;
  font-weight: 600;
}
.th-filter :deep(.el-input__wrapper),
.th-filter :deep(.el-select .el-input__wrapper) {
  box-shadow: none;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 0 8px;
  min-height: 28px;
}
.th-filter :deep(.is-focus) {
  border-color: #3e8cf0;
}
/* 1) 让每个节点行容器占满整行 */
.asset-tree :deep(.el-tree-node__content) {
  width: 100% !important;
  box-sizing: border-box;
}

/* 2) 覆盖任何 max-content/auto，强制 node-line 占满并做左右分栏 */
.asset-tree :deep(.node-line) {
  display: flex !important;
  align-items: center;
  gap: 8px;
  width: 100% !important; /* 关键 */
  padding: 0 6px; /* 给背景一点内边距 */
  border-radius: 8px; /* 背景圆角 */
}

/* 3) 左右区域：左侧可伸展，右侧贴右对齐 */
.asset-tree :deep(.node-left) {
  flex: 1 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.asset-tree :deep(.node-right) {
  flex: 0 0 auto;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* 4) 把“悬浮/选中”的背景直接加在 node-line 上（就能覆盖到右侧徽章） */
.asset-tree :deep(.el-tree-node__content:hover .node-line) {
  background: #f3f7ff;
}
.asset-tree
  :deep(
    .el-tree--highlight-current
      .el-tree-node.is-current
      > .el-tree-node__content
      .node-line
  ) {
  background: #eaf2ff;
}

/* 5) 字体恢复正常权重（避免看起来偏细） */
.asset-tree :deep(.node-left .name-colored) {
  font-weight: 400 !important;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 6) 如你之前还有这两条，请删除或确保被上面的规则覆盖：
   .asset-tree :deep(.el-tree-node__content){ width:auto !important; }
   .asset-tree :deep(.node-line){ width:max-content; }
*/
/* 取消 el-dialog footer 默认右对齐 */
/* 取消 el-dialog footer 默认右对齐 */

/* 建议加在当前组件的 <style scoped> 里 */
/* 放到 <style scoped> */
.op-grid {
  display: grid;
  grid-template-columns: repeat(3, 22px); /* 一行3个 */
  grid-auto-rows: 22px;
  gap: 6px;
  justify-content: center;
}

.op-cell {
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center; /* 让内容在方块里水平垂直居中 */
}

/* 关键：让 tooltip 的触发器占满方块，去掉基线影响 */
:deep(.op-cell .el-tooltip__trigger) {
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
}

.op-btn {
  width: 100%;
  height: 100%;
  padding: 0 !important;
  min-width: 0;
  line-height: 1; /* 去掉基线高度差 */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* 统一图标尺寸与外边距 */
:deep(.op-btn .el-icon) {
  margin: 0 !important;
  font-size: 16px;
}

/* 每个单元格里把多条备件堆成多行，并保证行高一致 */
.stack {
  display: grid;
  gap: 6px;
}
.stack .line {
  display: flex;
  align-items: center;
  min-height: 24px;
}
.num-col .line {
  justify-content: center;
}
.one-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* .spares-grid {
  display: grid;
  gap: 6px;
}
.sp-head,
.sp-row {
  display: grid;
  grid-template-columns: 1fr 90px 100px;
  column-gap: 8px;
  align-items: center;
}
.sp-head {
  font-size: 12px;
  color: #909399;
}
.sp-row .name {
  line-height: 20px;
}
.sp-row .num {
  text-align: right;
}
.one-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
} */

/* 内容区：即有滚动也不挤窄 */
:deep(.sp-b) {
  max-height: 160px;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-gutter: stable; /* ✅ 关键：预留滚动条宽度 */
}

/* 其余保持原来的三列网格 */
:deep(.sp-h),
:deep(.sp-r) {
  display: grid;
  grid-template-columns: minmax(100px, 1fr) 90px 90px;
  column-gap: 12px;
  align-items: center;
}
/* 表头右侧留出与滚动条同宽的空隙（兼容方案） */
:deep(.sp-h) {
  padding-right: var(--sbw, 0px);
}

/* 吸底条外层：不抢布局 */
.pagination-affix-wrap {
  /* 不需要额外样式 */
}

/* 吸附在底部时的外观 */
.pagination-affix {
  background: #fff;
  border-top: 1px solid #ebeef5;
  padding: 8px 12px;
  box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.04);
  z-index: 20; /* 盖住表格滚动条 */
}

/* 给页面内容留出高度，避免最后一行被分页挡住 */
.app-container {
  padding-bottom: 64px; /* 视你的分页高度微调 */
}

/* 5 列：名称 / 编码 / 可用 / 总数 / 使用；全部左对齐 */

.spares-grid {
  display: grid;
  grid-template-columns: repeat(var(--cols, 5), minmax(0, 1fr));
  gap: 8px 12px;
  width: 100%;
}

/* 表头和单元都用 flex，默认水平居中 */
.spares-grid .sp-head,
.spares-grid .sp-cell {
  display: flex;
  align-items: center;
  justify-content: center; /* 默认居中 */
}

/* 名称/编码两列改为左对齐 */
.spares-grid .sp-head.name,
.spares-grid .sp-head.code,
.spares-grid .sp-name,
.spares-grid .sp-code {
  justify-content: flex-start;
}

/* 编码胶囊：更易读 */
.code-pill {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono",
    monospace;
  background: #f5f7fa !important;
  border-color: #e4e7ed !important;
  border-radius: 6px !important;
  height: 22px;
  display: inline-flex;
  align-items: center;
}

/* 数字徽章：等宽数字 + 语义色 */
.badge {
  display: inline-block;
  padding: 0 6px;
  line-height: 20px;
  height: 20px;
  border-radius: 6px;
  border: 1px solid #dcdfe6;
  background: #f6f7fb;
  font-size: 12px;
  font-variant-numeric: tabular-nums; /* 等宽数字，列更齐 */
}
.badge.info {
  background: #ecf5ff;
  border-color: #d9ecff;
} /* 可用 */
.badge.warn {
  background: #fff7e6;
  border-color: #ffe3bf;
} /* 使用 */

.cas-opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
}
.cas-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cas-badge {
  flex: 0 0 auto;
}

/* 选择框内 tag 的小样式（可选） */
.sel-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.sel-type {
  color: #94a3b8;
  font-size: 12px;
}

.spare-table-wrapper {
  width: 100%;
  margin-top: 4px;
}

.spare-add-btn {
  text-align: right;
  margin-top: 10px;
}

.el-table.spare-table {
  border-radius: 6px;
  overflow: hidden;
}

.spare-header {
  background-color: #f8f9fb !important;
  font-weight: 500 !important;
  color: #303133 !important;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
.input-400 {
  width: 400px;
}
</style>
